﻿using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using System.Web;
using System.Web.Caching;
using ElectronicCommerce.Framework.Utility;


namespace ElectronicCommerce.Framework.Caching.DoubleCacheProvider
{
    /// <summary>
    /// Double Cache Provider
    /// </summary>
    internal class DoubleCacheProvider:ICacheProvider
    {
        #region Fileds

        private readonly DoubleCacheCollection collection;
        private readonly AspNetCacheProvider.AspNetCacheProvider  provider;

        #endregion

        #region Constructors

        public DoubleCacheProvider(string keyPreFix, int defaultExpireSeconds, int maximumElementsInCache
            , int numberToRemoveWhenScavenging)
        {
            provider = new AspNetCacheProvider.AspNetCacheProvider(keyPreFix, defaultExpireSeconds, maximumElementsInCache
            , numberToRemoveWhenScavenging);
            collection = new DoubleCacheCollection();
        }

        #endregion

        #region Properties

        public int DefaultExpireSeconds
        {
            get { return provider.DefaultExpireSeconds; }
        }

        public string KeyPreFix
        {
            get { return provider.KeyPreFix; }
        }

        public string Name
        {
            get { return provider.Name; }
        }

        public int MaximumElementsInCache
        {
            get { return provider.MaximumElementsInCache; }
        }

        public int NumberToRemoveWhenScavenging
        {
            get { return provider.NumberToRemoveWhenScavenging; }
        }

        #endregion

        #region ICacheProvider Members

        #region Insert

        public void Insert(string key, object obj)
        {
            Insert(key, obj, null, null, null);
        }

        public void Insert(string key, object obj, bool defaultExpire)
        {
            if (defaultExpire)
            {
                Insert(key, obj, DefaultExpireSeconds, null, null);
            }
            else
            {
                Insert(key, obj, null, null, null);
            }
        }

        public void Insert(string key, object obj, int seconds)
        {
            Insert(key, obj, seconds, null, null);
        }

        public void Insert(string key, object obj, ECCacheDependency dep)
        {
            Insert(key, obj, null, dep, null);
        }

        public void Insert(string key, object obj, Func<object> callBackFunction)
        {
            Insert(key, obj, null, null, callBackFunction);
        }

        #region Base

        private void Insert(string key, object obj, int? seconds, ECCacheDependency dep, Func<object> callBackFunction)
        {
            key = MakeUpInnerKey(CleanInnerKey(key));
            CheckPerformance();
            CacheDependency aspnetDependency = null;
            if (dep != null)
            {
                if (dep.FileNames.Length == 1)
                {
                    aspnetDependency = new CacheDependency(dep.FileNames[0]);
                    collection.Add(key, dep.FileNames[0], obj);
                }
                else if (dep.FileNames.Length > 1)
                {
                    aspnetDependency = new CacheDependency(dep.FileNames);
                    collection.Add(key, obj, callBackFunction);
                }
            }
            else
            {
                collection.Add(key, obj, callBackFunction);
            }
            HttpRuntime.Cache.Insert(key, obj, aspnetDependency,
                seconds.HasValue
                ?
                    DateTime.Now.AddSeconds(1D * seconds.Value)
                :
                    Cache.NoAbsoluteExpiration
                , TimeSpan.Zero, CacheItemPriority.Normal, Notify);
        }

        #endregion

        #endregion

        #region Search

        public object Get(string key)
        {
            if (provider.Contains(CleanInnerKey(key)))
            {
                return provider.Get(CleanInnerKey(key));
            }
            return collection[MakeUpInnerKey(CleanInnerKey(key))];
        }

        #endregion

        #region Delete

        public void Clear()
        {
            provider.Clear();
            collection.Clear();
        }

        public void Remove(string key)
        {
            provider.Remove(CleanInnerKey(key));
            collection.Remove(MakeUpInnerKey(CleanInnerKey(key)));
        }

        public void RemoveByPattern(string pattern)
        {
            if (string.IsNullOrEmpty(pattern))
            {
                return;
            }

            provider.RemoveByPattern(pattern);

            if (pattern.StartsWith("^"))
            {
                pattern = "^" + string.Concat(KeyPreFix, pattern.Substring(1));
            }
            else
            {
                pattern = string.Concat(KeyPreFix, pattern);
            }

            var regex = new Regex(pattern, RegexOptions.IgnoreCase | RegexOptions.Singleline);

            foreach (string key in collection.Keys)
            {
                if (regex.IsMatch(key))
                {
                    collection.Remove(key);
                }
            }
        }

        #endregion

        #region Other methods

        public bool Contains(string key)
        {
            return provider.Contains(CleanInnerKey(key))
                ||
                collection.ContainsKey(MakeUpInnerKey(CleanInnerKey(key)));
        }

        public List<ICacheItem> GetCurrentCacheItems()
        {
            return provider.GetCurrentCacheItems();
        }

        public void CheckPerformance()
        {
            provider.CheckPerformance();
        }

        #endregion

        #endregion

        #region Helper

        private string MakeUpInnerKey(string key)
        {
            return string.Concat(KeyPreFix, key);
        }

        private string CleanInnerKey(string key)
        {
            return key.Replace(KeyPreFix, "");
        }

        private void Notify(string key, object value, CacheItemRemovedReason reason)
        {
            if (reason == CacheItemRemovedReason.DependencyChanged)
            {
                var item = DoubleCacheCollection.GetItem(key);
                if (item == null || item.IsUpdating)
                {
                    return;
                }
                if (item.CallbackFunc != null)
                {
                    item.IsUpdating = true;
                    object obj = item.CallbackFunc.Invoke();
                    item.DisplayCacheItem = obj;
                    item.CacheItem = obj;
                    item.IsUpdating = false;
                }
                else if (!string.IsNullOrEmpty(item.FilePath))
                {
                    try
                    {
                        item.IsUpdating = true;
                        var currentItem = value as ICacheItem;
                        if (currentItem == null)
                        {
                            return;
                        }
                        object obj = ObjectXmlSerializer.LoadFromXml(item.FilePath, currentItem.Object.GetType());
                        item.DisplayCacheItem = obj;
                        item.CacheItem = obj;
                    }
                    catch
                    {
                        return;
                    }
                    finally
                    {
                        item.IsUpdating = false;
                    }
                }
            }
        }

        #endregion
    }
}
